Skip to content
On this page

쏙쏙 들어오는 함수형 코딩

도서 링크: https://www.yes24.com/Product/Goods/108748841

쏙쏙 들어오는 함수형 코딩

3장 액션과 계산, 데이터의 차이를 알기

액션, 계산, 데이터

액션: 부수효과가 있는 함수, 실행 시점과 횟수에 의존
계산: 순수 함수
데이터: 이벤트에 대한 사실

일상 생활에 액션, 계산, 데이터 적용하기

자칫 잘못 계산시 모든 것이 액션 처럼 취급될 수 있음. 하지만 세분화해서 하나하나의 액션을 확인해보면, 내부에는 데이터와 계산이 존재

계산 단계가 잘 인지되지 않는 이유

계산 과정이 우리 사고 과정 중에 존재하기 때문에 드러나지 않는다.
결정, 판단이 들어가는 경우에는 대부분 계산이 필요하다.

액션은 코드 전체로 퍼져나간다.

액션을 호출하는 함수도 결국에는 액션이 된다.

액션의 형태

  • 함수 호출
  • 메서드 호출
  • 생성자
  • 표현식
  • 상태

4장 액션에서 계산 빼내기

함수형 프로그래밍과 잘 맞는 제안

  • DOM 업데아트와 비즈니스 규칙 분리하기
  • 전역 변수 없애기
  • 어떤 실행 환경에서도 돌아가는 코드 작성 하기
  • 함수가 결괏값을 리턴하기

5장 더 좋은 액션 만들기

비즈니스 요구 사항과 설계를 맞추기

  • 현재 요구 사항은 장바구니에 담긴 제품을 주문할때 무료 배송인지 확인하는 것
  • 현재 코드는 장바구니가 아닌 제품의 합계 + 가격 을 해서 확인하고 있음
  • 따라서 calc_total 이라는 함수를 통해, 장바구니의 합계 금액을 알아내는 함수의 중복을 제거할 수 있음

코드의 라인 수와 좋은 코드의 관계

  • 코드의 라인 수가 좋은 코드의 지표일 수 있음
  • 하지만 좋은 코드를 측정하는 지표는 라인 수 외에도 많음
  • 작은 함수는 이해하기 쉽고, 재사용에 용이함

배열의 복사 비용

  • 배열 복사는 배열의 변경보다 비용이 많이 드는 것은 사실
  • 하지만, GC 를 통한 메모리 최적화 처리가 됨
  • 복사본을 사용할 때의 많은 장점이 있음
    • 책 후반부 6장, 7장에서 다룰 예정...?

원칙: 암묵적 입력과 출력은 적수록 좋다

  • 인자가 아닌 모든 입력은 암묵적 입력
  • 리턴값이 아닌 모든 출력은 암묵적 출력
  • 계산으로 만들기 위해 암묵적 입출력을 없애는 것을 액션에도 적용 가능
  • 액션을 계산으로 변경하지 못하더라도 암묵적 입출력을 줄이면 테스트가 용이해짐

원칙: 설계는 엉켜있는 코드를 푸는 것이다

분리되어 있는 것을 조립하는 것은 쉽다. 오히려 잘 분리하는 방법을 찾기 어렵다.

  • 재사용하기 쉽다.
  • 유지보수하기 쉽다.
  • 테스트하기 쉽다.

add_item 함수 더 좋은 설계 적용 시키기

기존 코드에서는 add_item 함수는 cart, item 구조를 모두 알고 있었음.
item 을 만들어 주는 함수를 별도로 분리하면, add_item 함수의 역할은 배열의 마지막에 신규 값을 추가해주는 역할만 하게 됨.
즉 유틸리티 함수가 됨.
카피온 라이트: 배열을 복사 후, 복사본을 조작해서 리턴하는 것

6장 변경 가능한 데이터 구조를 가진 언어에서 불변성 유지하기

카피 온 라이트 원칙 세단계

  1. 복사본 만들기
  2. 복사본 변경하기
  3. 복사본 리턴하기

카피 온 라이트로 쓰기를 읽기로 바꾸기

  • 읽기
    • 데이터에서 정보를 가져온다
    • 데이터를 바꾸지 않는다
  • 쓰기
    • 데이터를 바꾼다.

카피온 라이트는 쓰기를 읽기로 바꾼다.

데이터의 불변성에 따른 계산과 액션 분리

  • 변경 가능한 데이터를 읽는 것은 액션
  • 쓰기는 데이터를 변경 가능한 구조로 만듬
  • 어떤 데이터에 쓰기가 없다면 데이터는 변경 불가능한 데이터
  • 불변 데이터 구조를 읽는 것은 계싼
  • 쓰기를 읽기로 바꾸면 코드에 계산이 많아짐

시간에 따라 변하는 상태가 있다.

모든 어플리케이션은 결국 변경 가능한 데이터가 필요로 한다.

불변 데이터 구조는 충분히 빠르다.

  • 불변 데이터 구조를 사용하고 속도가 느린 부분이 있다면 그때 최적화를 한다.
  • 가비지 콜렉터는 매우 빠르다.
  • 생각보다 많이 복사하지 않는다.
    • 배열을 복사하게 되면 참조만 복사하게 된다. (얕은 복사)
    • 얕은 복사는 같은 메모리를 가리키는 참조에 대한 복사본을 만든다.
  • 함수형 프로그래밍 언어에는 빠른 구현체가 있다.
    • 특정 언어들은 불변 데이터 구조를 자체적으로 지원

객체에 대한 카피 온 라이트

  • Object.assign 이라는 함수를 통해서 객체를 복사 가능함

얕은 복사와 구조적 공유

  • 중첩데이터: 데이터 구조 안에 데이터 구조가 있는 것을 말함
  • 얕은 복사: 중첩 데이터에서 최상위 데이터 구조만 복사
  • 구조적 공유: 두 중첩된 데이터 구조에서 안쪽 데이터가 같은 데이터를 참조하는 것

중첩된 데이터에서 얕은 복사를 했기떄문에 구조적 공유가 되었다.

7장 신뢰할 수 없는 코드를 쓰면서 불변성 지키기

방어적 복사 규칙

  1. 데이터가 안전한 코드에서 나갈 때 복사하기
  2. 안전한 코드로 데이터가 들어올 때 복사하기

방어적 복사를 사용하는 케이스

  • 언제 사용
    • 신뢰할 수 없는 코드와 데이터를 주고 받아야할 떄 방어적 복사를 사용한다.
  • 어디서 사용
    • 안전지대의 경계에서 데이터를 오고 갈 때 방어적 복사를 사용
  • 복사 방식
    • 깊은 복사를 사용
  • 규칙
    • 안전지대로 들어오는 데이터에 깊은 복사를 만듬
    • 안전지대에서 나가는 데이터에 깊은 복사를 만듬

8장 계층형 설계

소프트웨어 설계란?

코드를 만들고, 테스트하고, 유지보수하기 쉬운 프로그래밍 방법을 선택하기 위해 미적 감각을 사용하는 것

계층형 설계란?

소프트웨어를 계층으로 구성하는 기술
비즈니스 규칙 - 장바구니를 위한 동작들 - 카피 온 라이트 - 언어에서 지원하는 배열 관련 기능

12장. 함수형 반복

기존 반복문 로직 forEach 로 변경

function emailsForCustomers(customers, goods, bests) {
var emails = [];
for (var i = 0; i < customers.length; i++) {
var customer = customers[i];
var email = emailForCustomer(customer, goods, bests);
emails.push(email);
}
return emails;
}
function emailsForCustomers(customers, goods, bests) {
var emails = [];
forEach(customers, function (customer) {
var email = emailForCustomer(customer, goods, bests);
emails.push(email);
});
return emails;
}

map 함수 도출하기

function emailsForCustomers(customers, goods, bests) {
var emails = [];
for (var i = 0; i < customers.length; i++) {
var customer = customers[i];
var email = emailForCustomer(customer, goods, bests);
emails.push(email);
}
return emails;
}
function biggestPurchasePerCustomer(customers) {
var purchases = [];
for (var i = 0; i < customers.length; i++) {
var customer = customers[i];
var purchase = biggestPurchase(customer);
purchases.push(purchase);
}
return purchases;
}
// map 함수 도출
function emailsForCustomers(customers, goods, bests) {
return map(customers, function (customer) {
return emailForCustomer(customer, goods, bests);
});
}
function map(array, f) {
var newArray = [];
forEach(array, function (element) {
newArray.push(f(element));
});
return newArray;
}

함수형 도구: map()

function map(array, f) {
var newArray = [];
forEach(array, function (element) {
newArray.push(f(element));
});
return newArray;
}
  • 배열과 함수를 인자로 받아서
  • 빈배열을 만든 후
  • 원래 배열 항목으로 새로운 항목을 만들기 위해 f() 함수를 호출한 후,
  • 원래 배열 항목에 해당하는 새로운 항목을 추가
  • 새로운 배열을 리턴

함수를 전달하는 세가지 방법

  • 전역으로 정의하기
  • 지역적으로 정의하기
  • 인라인으로 정의하기

filter 함수 도출하기

function selectBestCustomers(customers) {
var newArray = [];
forEach(customers, function (customer) {
if (customer.purchases.length >= 3)
newArray.push(customer);
});
return newArray;
}
function selectCustomersAfter(customers, date) {
var newArray = [];
forEach(customers, function (customer) {
if (customer.signupDate > date)
newArray.push(customer);
});
return newArray;
}
// filter 함수 도출
function selectBestCustomers(customers) {
return filter(customers, function (customer) {
return customer.purchases.length >= 3;
});
}
function filter(array, f) {
var newArray = [];
forEach(array, function (element) {
if (f(element))
newArray.push(element);
});
return newArray;
}

함수형 도구: filter()

function filter(array, f) {
var newArray = [];
forEach(array, function (element) {
if (f(element))
newArray.push(element);
});
return newArray;
}
  • 배열과 함수를 인자로 받아서
  • 빈배열을 만든 후
  • f() 함수를 호출한 후, 결과 배열에 넣을지 확인
  • 조건에 맞으면 원래 항목을 결과 배열에 추가
  • 결과 배열을 리턴

reduce 함수 도출하기

function countAllPurchases(customers) {
var total = 0;
forEach(customers, function (customer) {
total = total + customer.purchases.length;
});
return total;
}
function concatenateArrays(arrays) {
var result = [];
forEach(arrays, function (array) {
result = result.concat(array);
});
return result;
}
// reduce 함수 도출
function countAllPurchases(customers) {
return reduce(customers, 0, function (total, customer) {
return total + customer.purchase.length;
});
}
function reduce(array, init, f) {
var accum = init;
forEach(array, function (element) {
accum = f(accum, element);
});
return accum;
}

함수형 도구: reduce()

function reduce(array, init, f) {
var accum = init;
forEach(array, function (element) {
accum = f(accum, element);
});
return accum;
}
  • 배열, 초기값, 함수를 인자로 받아서
  • 초기값을 누적값으로 설정한 후
  • 누적 값을 계산하기 위해, 현재 값과 배열의 항목으로 f() 함수를 호출
  • 누적된 값을 리턴

예제: 문자열 합치기

reduce(strings, "", function (accum, string) {
return accum + string;
});

연습 문제: min, max

function min(numbers) {
return reduce(numbers, Number.MAX_VALUE, function (m, n) {
if (m < n) return m;
else return n;
});
}
function max(numbers) {
return reduce(numbers, Number.MIN_VALUE, function (m, n) {
if (m > n) return m;
else return n;
});
}

연습 문제: 상황에 따른 map, filter, reduce

map 함수에 빈배열을 넘기면?
-> []

filter 함수에 빈배열을 넘기면?
-> []

reduce 함수에 빈배열을 넘기면?
-> 초기값

map() 함수에 인자를 그대로 리턴하는 함수를 넘기면?
-> 얕은 복사가 된 array

filter() 함수에 항상 true를 리턴하는 함수를 넘기면?
-> 얕은 복사가 된 array

filter() 함수에 항상 false를 리턴하는 함수를 넘기면?
-> []

Iteration protocols (이터레이션 프로토콜)

이터러블 프로토콜
이터러블: Symbol.iterator 메소드를 구현하거나 프로토타입 체인에 의해 상속한 객체
Symbol.iterator 메소드는 이터레이터를 반환

이터레이터 프로토콜
이터레이터: next 메소드를 소유하며 next 메소드를 호출하면 이터러블을 순회하며 value, done 프로퍼티를 갖는 이터레이터 리절트 객체를 반환하는 객체

const array = [1, 2, 3];
const iterator = array[Symbol.iterator]();
console.log('next' in iterator); // true
console.log(iterator.next()); // {value: 1, done: false}
console.log(iterator.next()); // {value: 2, done: false}
console.log(iterator.next()); // {value: 3, done: false}
console.log(iterator.next()); // {value: undefined, done: true}

이터레이션을 이용한 reduce 구현

const reduce = (f, acc, iter) => {
if (!iter) {
iter = acc[Symbol.iterator]()
acc = iter.next().value
}
for (const a of iter) {
acc = f(acc, a)
}
return acc
}
최근 수정 일: 2023. 10. 24.